#include "graph_combine.h"
#include <algorithm>    // std::sort

void graph_combine::gen_mc_delay(unsigned int ith_mc, class ff_graph& ff_obj)
{
    //default_random_engine normal_generator;
    normal_distribution<float> distribution(0, 1);

    // the Mersenne Twister with a popular choice of parameters
    typedef std::mt19937 MyRNG;  
    MyRNG rng;                   // e.g. keep one global instance (per thread)

    // generate deterministic delay for each simulation
    mc_vertex_delay.assign(ff_obj.get_vertex_num(), 0);

    for (unsigned int i = 0; i < ff_obj.get_vertex_num(); i++) 
    {
        // mean of random delay
        /*
        * Implementation note:
        *  We should simulate all falling and rising delays
        *  Current design is for simplicity
        */
        if (ff_obj.vertex_delay[i][0] == 0)
        {
            mc_vertex_delay[i] = 0.0;
            continue;
        }

        //unsigned int delay_num = ff_obj.vertex_fanin.size() * 2;
        unsigned int delay_num = 1;

        vector <float> delays; //store all delays for single gate 
        delays.assign(delay_num, 0);

        //reproduce seed
        unsigned long rand_seed = (ith_mc + 1) * mc_vertex_seed[i];
        rng.seed(rand_seed);

        for (unsigned int k = 0; k < delay_num; k++)
        {
            //delays[k] = ff_obj.vertex_delay[i][0] - k* 0.1; //mean 
            delays[k] = ff_obj.vertex_delay[i][0];
            for (unsigned int j = 1; j < VARNUM; j++) 
            {
                float rand = distribution(rng);
                delays[k] += ff_obj.vertex_delay[i][j] * rand;
            }
        }

        float delay = *max_element(delays.begin(),delays.end());

        if (delay < 0) {
            delay = 0.0; //in case generate negative delay
        }
        mc_vertex_delay[i] = delay;
    }
}

void graph_combine::load_vertex_mapping(char* map_file)
{
    FILE *fp = fopen(map_file, "r");

    unsigned int map, color;

    while (!feof(fp)) 
    {    
        fscanf(fp," %d %d\n", &map, &color);
        mapping.push_back(map);
    }
}

void graph_combine::load_vertex_delay(char* benchmark, class ff_graph& ff_obj)
{
    char delay_file[50];
    strcpy(delay_file, "../benchmark/BENCH/local/");
    strcat(delay_file, benchmark);

    //phase two gray and black
    strcat(delay_file, "_color_p2.delay"); 

    FILE *fp = fopen(delay_file, "r");

    rewind(fp);

    unsigned int vertex_num;
    unsigned int i = 0; //row index
    float pca; //single pca component

    fscanf(fp," %d %d \n", &black_num, &delay_column);

    //verify
    //assert(vertex_num = vertex_set.size());
    vertex_num = vertex_set.size();

    ff_obj.set_vertex_num(vertex_num);
    ff_obj.vertex_delay.assign(vertex_num, vector<float>(delay_column, 0));
    ff_obj.tp_order.clear();
    ff_obj.initial_vertex_color();

    unsigned int v = 0;
    //for(unsigned int i = 0; i < black_num; i++)
    while (!feof (fp)) 
    {
        unsigned int vertex;
        fscanf(fp," %d", &vertex);
        //ff_obj.tp_order.push_back(i); 
        //vertex_set.push_back(vertex); //original index
        mc_vertex_seed.push_back(100 + vertex); // store random_seed

        //fscanf(fp," %f", &pca); //check the mean delay
        for(; v < vertex_num; v++)
        {
            if (vertex == vertex_set[v])
            {
                //ff_obj.set_vertex_color(v, 2); // set black vertex
                for(unsigned int j = 0; j < delay_column; j++)
                {
                    fscanf(fp," %f", &pca);
                    //assert(i < gate_num); //for BENCH
                    ff_obj.vertex_delay[v][j] = (float)pca;
                }
                fscanf(fp," \n");
                break;
            }
        } 
    }
    fclose(fp);
}

void graph_combine::integrate_graph(FILE* fp, char *black_file, 
                                class ff_graph& ff_obj)
{
    FILE *fp_black = fopen(black_file, "r");

    if (fp_black == NULL)  perror ("Error opening black dag file");

    rewind(fp_black);

    struct edge e;
    unsigned int source;
    unsigned int sink;
    e.delay = 0.0;

    unsigned int edge_num = 0;
    ff_obj.edge_set.clear();

    vector<unsigned int> temp_vertex; //to find vertex_set

    fscanf(fp_black," %d %d \n", &black_num, &black_edge_num);

    //first edges are black edge
    while (!feof(fp_black)) 
    {
        fscanf(fp_black,"%d %d \n", &source, &sink);
        //load black edge
        e.in = source;
        e.out = sink;
        ff_obj.edge_set.push_back(e);

        temp_vertex.push_back(e.in);
        temp_vertex.push_back(e.out);
    }

    fclose(fp_black);

    // vertex_relabeling
    fscanf(fp," %d", &edge_num);

    for (unsigned int i = 0; i < edge_num; i++)
    {   
        fscanf(fp,"%d %d \n", &source, &sink);

        e.in = mapping[source];
        e.out = mapping[sink];
        e.delay = 0.0;

        /*for (unsigned int j = 0; j < vertex_set.size(); j++)
        {
            if (source == vertex_set[j])
            {
                e.in = j;
            }

            if (sink == vertex_set[j])
            {
                e.out = j;
            }
        }*/

        ff_obj.edge_set.push_back(e);

        temp_vertex.push_back(e.in);
        temp_vertex.push_back(e.out);
    }

    //find vertex_num
    vertex_set.clear();
    int min = 0;
    vertex_set.push_back(min);
    sort(temp_vertex.begin(), temp_vertex.end());

    for (unsigned int i = 1; i < temp_vertex.size(); i++)
    {
        if (temp_vertex[i] > min)
        {
            min = temp_vertex[i];
            vertex_set.push_back(min);
        }
    }

    ff_obj.set_vertex_num(vertex_set.size());
    ff_obj.set_edge_num(ff_obj.edge_set.size());

    cout<<"vertex_num = "<<ff_obj.get_vertex_num()<<endl; 
    cout<<"edge_num = "<<ff_obj.get_edge_num()<<endl; 

    //relabel edge 
    for (unsigned int i = 0; i < ff_obj.get_edge_num(); i++)
    {
        for (unsigned int j = 0; j < vertex_set.size(); j++)
        {
            if (ff_obj.edge_set[i].in == vertex_set[j])
            {
                ff_obj.edge_set[i].in = j;
            }

            if (ff_obj.edge_set[i].out == vertex_set[j])
            {
                ff_obj.edge_set[i].out = j;
            }
        }
    }

    //ff_obj.gen_fanins();
    //ff_obj.gen_fanouts();

}

float graph_combine::sta_pert_reduce(class ff_graph& ff_obj)
{
    //intial arrive time 
    vertex_arrive_time.assign(ff_obj.get_vertex_num(), 0);

    //assert(ff_obj.tp_order.size() == ff_obj.get_vertex_num());
    for (unsigned int vertex = 0; vertex < ff_obj.get_vertex_num(); vertex++)
    {
        vector<edge>::iterator edge_it;
        for (edge_it = ff_obj.vertex_fanin[vertex].begin(); 
            edge_it != ff_obj.vertex_fanin[vertex].end(); ++edge_it)
        {
            unsigned int source = (*edge_it).in;
            //accumulate path delay
            float temp_delay = (*edge_it).delay + vertex_arrive_time[source];

            //update arrive time
            vertex_arrive_time[vertex] = 
                (vertex_arrive_time[vertex] > temp_delay) 
                ? vertex_arrive_time[vertex] : temp_delay;
        }
    }

    //return maximum path weight
    return vertex_arrive_time[ff_obj.get_vertex_num() - 1];
}

float graph_combine::mc_ssta_reduce(char* benchmark, unsigned int mc_num, 
                                    class ff_graph& ff_obj)
{
    char rd_file[50];
    char rd_delay_file[50];
    char map_file[50];
    char black_file[50];


    strcpy(rd_file, "../benchmark/BENCH/local/");
    strcpy(rd_delay_file, "../benchmark/BENCH/local/");
    strcpy(map_file, "../benchmark/BENCH/local/");
    strcpy(black_file, "../benchmark/BENCH/local/");

    strcat(rd_file, benchmark);
    strcat(map_file, benchmark);
    strcat(black_file, benchmark);
    strcat(rd_delay_file, benchmark);

    strcat(map_file, "_p2.map"); //current take care of phase 2

    load_vertex_mapping(map_file);

    strcat(rd_file, "_rd_p2.dag"); //phase two

    FILE *fp = fopen(rd_file, "r");

    if (fp == NULL)  perror ("Error opening graph reduction file");

    strcat(black_file, "_black_p2.dag"); //current take care of phase 2
    integrate_graph(fp, black_file, ff_obj); //load dag and build new graph
    fclose(fp);

    load_vertex_delay(benchmark, ff_obj);

    // for vertex delay generation
    ff_obj.gen_fanins();

    float clock = 12.0;
    float yield = 0;

    unsigned int m = 0;

    strcat(rd_delay_file, "_rd_edge_p2.delay");

    ifstream ifs(rd_delay_file, ios::binary);
        
    for (unsigned int n = 0; n < mc_num; n++)
    {   
        gen_mc_delay(n, ff_obj);

        //assign edge delay
        for (unsigned int i = 0; i < black_edge_num; i++)
        {
            unsigned fanin_v = ff_obj.edge_set[i].in;
            ff_obj.edge_set[i].delay = mc_vertex_delay[fanin_v];
        }

        //reduced graph edge delay
        for (unsigned int i = black_edge_num; i < ff_obj.edge_set.size(); i++)
        {
            float delay;
            ifs.read(reinterpret_cast<char*>(&delay), 4);
            ff_obj.edge_set[i].delay = delay;
        }

        ff_obj.gen_fanins();
        ff_obj.gen_fanouts();
       
        //update maximum path delay
        float max_path_delay = sta_pert_reduce(ff_obj);

        if (clock > max_path_delay)
        {
            m++;
        }
    }

    ifs.close();

    //compute timing yield
    return (float) m/mc_num;
}